![]() |
![]() |
|
Diese Punkte lassen sich in zwei logische Kategorien zusammenfassen: 1. Metadaten, die eine Assembly ganzheitlich beschreiben und als Manifest der Assembly bezeichnet werden
Assemblies können nicht nur Module für Typen enthalten, oft gehören auch Ressourcen dazu, beispielsweise BMP-, JPEG- und HTML-Dateien, die von der Assembly zur Laufzeit benötigt werden. Diese Dateien sind dann ebenfalls Bestandteil einer Assembly. Es lässt sich auch eine Assembly vorstellen, die weder Code noch Ressourcen enthält – ob eine solche Assembly allerdings noch Sinn macht, lassen wir dahingestellt. Was letztendlich eine Assembly erst zu einer solchen werden lässt, sind die Metadaten über sich selber, also das Manifest. In einer einfachen Assembly sind alle Informationen in einer EXE- oder DLL-Datei enthalten, wie in Abbildung 14.1 am Beispiel der fiktiven Assembly GraphicObjects.dll dargestellt.
Abbildung 14.1 Struktur einer Einzeldatei-Assembly Eine Mehrfachdatei-Assembly ist strukturell komplexer aufgebaut. In welcher Kombination IL-Code und Ressourcen zusammengefasst werden, steht dem Entwickler frei und orientiert sich an Gesichtspunkten, die später noch erörtert werden. Unabhängig davon, aus wie vielen Dateien die Assemblierung gebildet wird, kann sich das Manifest nur in einer Datei befinden. Ob diese Datei auch noch IL-Code enthält oder nicht, spielt keine Rolle. Sie können durchaus für das Manifest eine eigene Datei vorsehen und den IL-Code in zumindest einer weiteren verpacken. Die Typmetadaten sind aber auf jeden Fall an den Code gebunden und somit in derselben Datei enthalten. Abbildung 14.2 zeigt dieselbe Anwendung wie Abbildung 14.1, diesmal allerdings zu einer Mehrfachdatei-Assembly kompiliert. Der Programmcode ist in drei Dateien enthalten, zusätzlich bindet das Manifest noch eine Bitmap-Datei als Ressourcendatei ein. Beachten Sie bitte, dass das Manifest, die Metadaten der Assemblierung, nur in einer Datei enthalten sein kann – in der Abbildung ist es die Datei GraphicObjects.dll. Für eine von außen zugreifende Komponente bildet diese Datei damit auch gleichzeitig den Einstiegspunkt in die Assemblierung. Die Dateierweiterung der beiden anderen codeenthaltenden Dateien lautet .NETMODULE. Diese Dateien enthalten zwar Typdefinitionen, jedoch niemals das Manifest.
Abbildung 14.2 Struktur einer Mehrfachdatei-Assembly Manifest und MetadatenMetadaten sind binäre Informationen, die beim Kompilieren einer Datei, sei es in eine DLL- oder EXE-Datei, hinzugefügt werden und die Daten ganzheitlich beschreiben. Jeder Typ, den man innerhalb einer Assembly definiert oder einbindet, wird von den Metadaten erfasst. Zur Laufzeit einer Assembly werden die Metadaten in den Speicher geladen und von der Common Language Runtime dazu benutzt, die benötigten Informationen zu beziehen, die zur Erstellung und Verwendung eines Objekts erforderlich sind. Der Informationsgehalt der Metadaten ist vielseitiger Natur und lässt sich in zwei Gruppen einteilen:
Mit dem Manifest und den Typmetadaten verfügt die Common Language Runtime über genügend Informationen, um Klassen aus einer Datei zu laden, Objekte zu erstellen, Methodenaufrufe aufzulösen und auf Objektdaten zuzugreifen. Es spielt auch keine Rolle, in welcher Sprache die Bausteine einer Assembly entwickelt worden sind: Das Manifest verwischt die Spuren des zugrunde liegenden Quellcodes. Unter COM war zur binären Bindung eines Servers an einen Client noch Bindecode notwendig (IDL – Interface Definition Language), um eine gemeinsame Basis für die Kommunikation und den Datenaustausch beider Komponenten zu schaffen. Die Intermediate Language (IL) und die Common Language Runtime (CLR) schaffen mittels des Manifests die Voraussetzung für den problemlosen Austausch ohne den Aufbau solcher Behelfsbrücken. Der IL-DisassemblerSie können sich die Metadaten einer Assembly vom Typ EXE oder DLL sowie die der Module mit der Erweiterung NETMODULE ansehen, wenn Sie das mit Visual Studio 2005 gelieferte Tool ildasm.exe, den so genannten IL-Disassembler, an der Konsole aufrufen. Sie finden diese Datei in einem Unterordner der Visual Studio-Installation. Haben Sie die Standardvorgaben bei der Installation übernommen, wird es sich um \Programme\Microsoft Visual Studio 8\SDK\v2.0\bin handeln. Optional geben Sie beim Aufruf des Tools den Pfad zu einer Assembly an, beispielsweise:
Starten Sie den Disassembler ohne Dateiangabe, können Sie über das Menü Datei|Öffnen die zu inspizierende Assembly wählen. Wir wollen uns nun mit Hilfe des ILDASM-Tools das Manifest einer Konsolenanwendung ansehen, die neben dem Ausgabecode der Methode Main in derselben Quellcodedatei noch die Definition der Klasse ClassA enthält. In einer zweiten Quellcodedatei des Projekts ist die Klasse ClassB definiert. Um den Informationsgehalt im Disassembler zu verdeutlichen, enthält der Typ ClassB insgesamt drei Variablendeklarationen mit unterschiedlichen Sichtbarkeiten.
Der Name der Assembly sei MyAssembly. Beachten Sie bitte, dass in Main zu Demonstrationszwecken ein Objekt vom Typ DataColumn erstellt wird, also auf die standardmäßig eingebundene Datei System.Data.dll zugegriffen wird. Sehen wir uns jetzt an, was uns das ILDASM-Tool liefert, ohne dabei allzu sehr in die Details zu gehen. Unterhalb des Wurzelknotens, der den Pfad zu der Assemblierung angibt, ist – mit einem roten Dreieck gekennzeichnet – das Manifest angeführt. Darunter befindet sich der Knoten MyAssembly, der in der Abbildung bereits vollständig geöffnet ist. Das blaue Rechteck, das mit seinen drei nach rechts weisenden Linien an einen Stecker erinnert, symbolisiert Klassendefinitionen. Neben den Typmetadaten listet das Tool alle Variablen und Klassenmethoden auf – in unserem Beispiel nur die statische Methode Main aus Class1 sowie die mit .ctor bezeichneten Konstruktoren – und gibt den Sichtbarkeitsbereich der Variablen an. Der Rückgabewert der Methoden wird, getrennt durch einen Doppelpunkt, hinter dem Methodennamen angeführt. Werfen wir jetzt einen Blick auf das Manifest dieser Assemblierung. Ein Doppelklick auf den Manifest-Eintrag des Disassemblers öffnet ein weiteres Fenster, das die Metadaten der Assemblierung wiedergibt (siehe Abbildung 14.4).
Abbildung 14.3 Die Anzeige des ILDASM-Tools
Abbildung 14.4 Das Manifest einer Assembly Der Reihe nach werden alle externen Assemblies aufgelistet, von der die aktuelle Assembly abhängt. Dazu gehört die wichtigste aller Assemblies, die mscorlib (beschrieben durch die Datei mscorlib.dll). Ihr folgt die Assembly, die aufgrund der Erzeugung des Objekts vom Typ DataColumn ebenfalls von der Anwendung benutzt wird: System.Data (bzw. die Datei System.Data.dll). Da in der Assemblierung MyAssembly keine weitere externe Assembly eine Rolle spielt, werden auch keine anderen im Manifest aufgeführt. Der Liste der externen Assemblierungen schließt sich im Block
eine Liste diverser Attribute an, mit denen die Assemblierung beschrieben wird. Die Attribute können in der Datei AssemblyInfo.cs der Entwicklungsumgebung festgelegt werden. Sehen wir uns nun die Referenzangabe einer externen Assembly genauer an, die beispielsweise folgendermaßen lautet:
Weiter oben wurde bereits ein wesentlicher Unterschied zwischen einer privaten und einer gemeinsam genutzten, externen Assembly erwähnt: Eine gemeinsam genutzte Assembly verfügt über einen öffentlichen Schlüssel. Im Manifest wird dieser Schlüssel hinter dem Attribut .publickeytoken in geschweiften Klammern angegeben. Neben dem Namen einer Assemblierung trägt unter anderem auch der Schlüssel zur eindeutigen Identifizierung der Assembly bei und sichert gleichzeitig die Identität des Komponentenentwicklers. Auf dieses Thema werden wir später noch einmal zurückkommen. Die .NET-Laufzeitumgebung gewährleistet zur Vermeidung von Kompatibilitätsproblemen Parallelität, d. h., es dürfen mehrere gleichnamige, allerdings versionsunterschiedliche Assemblierungen nebeneinander existieren, ohne damit Konflikte zu verursachen. Die Versionsinformationen sind in einem standardisierten Format dargestellt, das im Manifest der referenzierenden Assemblierung mit dem Attribut .ver gekennzeichnet ist. Zur Laufzeit der Anwendung werden die Versionsinformationen von der Laufzeitumgebung zur Auflösung des angeforderten Typs umgesetzt. 14.1.2 Einzeldatei-Assemblies
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| csc.exe <Dateiliste> |
Das Ergebnis der Kompilierung ist standardmäßig eine EXE-Datei, kann aber durch den Optionsschalter /target beeinflusst werden, um andere Dateitypen zu erstellen. Wir kommen auf dieses Thema gleich noch zurück.
Nehmen wir an, ein Projekt würde sich aus den Quellcodedateien
| ClassA.cs (enthält die Definition der Klasse ClassA) |
| ClassB.cs (enthält die Definition der Klasse ClassB) |
zusammensetzen. Am einfachsten haben Sie es, wenn Sie an der Konsole in das Projektverzeichnis wechseln und die folgende Anweisung eingeben:
| csc ClassA.cs ClassB.cs |
Das Ergebnis der Kompilierung wird als EXE-Datei im Projektordner gespeichert. Da es sich bei dem Kompilat um eine .NET-Assembly handelt, beinhaltet die Ausgabedatei natürlich gleichzeitig auch das Manifest.
Standardmäßig orientiert sich der Name der resultierenden Programmdatei am Namen der Quellcodedatei, die im ersten Argument angegeben wird. Die obige Anweisung würde somit die Datei ClassA.exe erstellen. Sie können jedoch mit dem Optionsschalter /out den Ausgabenamen nach eigenem Ermessen festlegen. Soll die kompilierte Datei beispielsweise MyApp heißen, muss man den C#-Compiler wie folgt aufrufen:
| csc /out:MyApp.exe ClassA.cs ClassB.cs |
Als Separator zwischen dem Optionsschalter und dem auszugebenden Dateinamen dient der Doppelpunkt.
Der Compiler versetzt uns in die Lage, eine oder mehrere CS-Dateien gleichzeitig in eine EXE-Ausgabedatei zu kompilieren. Unter gewissen Umständen ist es erforderlich, abweichend von diesem Standard einen anderen Dateityp zu kompilieren. Dazu dient der Optionsschalter /target, der auf zwei verschiedene Arten gesetzt werden kann:
| /target:<Attribut> |
oder optional die Kurzform:
| /t:<Attribut> |
Unter <Attribut> kann man eins von insgesamt vier möglichen Attributen angeben, die Sie der folgenden Tabelle entnehmen können.
| Attribut | Beschreibung |
| exe | Erzeugt eine EXE-Datei (dies ist der Standard des Compilers). |
| library | Erzeugt eine Klassenbibliothek mit der Dateierweiterung DLL. |
| module | Erzeugt ein Modul mit der standardmäßigen Dateierweiterung NETMODULE. |
| winexe | Erzeugt ein Windows-Programm. |
Unabhängig davon, welches Attribut Sie dem Compiler als Option angeben, wird aus der Liste der aufgeführten CS-Dateien eine Einzeldatei-Assembly generiert, die auf jeden Fall neben den Typmetadaten auch den IL-Code enthält – wenn es sich um eine EXE- oder DLL-Datei handelt, zusätzlich auch noch das Manifest.
| Dem Attribut module mit der resultierenden Dateierweiterung kommt eine ganz besondere Bedeutung zu: Module dieses Typs sind die Bausteine einer Mehrfachdateiassemblierung, die nur Typmetadaten enthalten, jedoch kein Manifest. NETMODULE-Bausteine werden oft einfach nur als »Module« bezeichnet und gelten wegen des fehlenden Manifests nicht als Assembly. |
Mehrfachdatei-Assemblies können sowohl in eigenstartfähige Anwendungen mit der Dateierweiterung EXE als auch in den Typ einer Klassenbibliothek mit der Erweiterung DLL kompiliert werden. Es muss genau einen Baustein dieses Typs geben, alle anderen Bausteine der Anwendung – zumindest die, die IL-Code enthalten – haben die Dateierweiterung NETMODULE. Möglicherweise gesellen sich dazu noch die von der Anwendung benutzten Ressourcendateien.
Ein Mehrfachdatei-Assembly bietet sich an, wenn Sie beispielsweise eine über das Web zu verteilende Anwendung entwickeln und die Effizienz des Downloads gesteigert werden soll. Eine NETMODULE-Datei wird nur dann geladen, wenn in der Anwendung ein Typ benötigt wird, der sich in dem Modul befindet. Ein Mehrfachdatei-Assembly ist auch dann in Erwägung zu ziehen, wenn mehrere Entwickler an ein und demselben Projekt arbeiten, dabei aber unterschiedliche Entwicklungssprachen benutzen.
Obwohl eine Mehrfachdatei-Assembly aus zwei oder mehr Dateien gebildet wird, enthält sie nur ein Manifest, das sich entweder als Block in eines der kompilierten Module eingliedert oder als separate Datei vorliegt. Sehen Sie sich dazu Abbildung 14.5 an. Die Assembly setzt sich aus einer NETMODULE-Datei und einer DLL-Datei zusammen. Letztere enthält neben den Typmetadaten und dem IL-Code das Manifest. Bei der Assembly2 könnte es sich um dieselbe Anwendung handeln, mit dem Unterschied, dass das Manifest von sämtlichen anderen Elementen abgekoppelt in eine eigenständige Datei kompiliert worden ist.

Hier klicken, um das Bild zu vergrößern
Abbildung 14.5 Das Manifest in einer Mehrfachdatei-Assembly
Die Erkenntnis, dass Codemodule während des Kompilierungsprozesses in eigenständige Dateien kompiliert werden können, führt auch zu einer grundsätzlichen neuen Betrachtensweise hinsichtlich der Strukturierung eines Projekts. Wenn Sie sich von Anfang an sicher sind, eine Mehrfachdatei-Assembly entwickeln zu wollen, kommt der Überlegung über die sinnvolle Aufteilung der Typen auf mehrere Quellcodedateien eine weit reichende Bedeutung zu. Zur Kompilierzeit können nämlich die Quellcodedaten in beliebiger Kombination sowohl in NETMODULE-Bausteine als auch in EXE- oder DLL-Dateien gepackt werden.
Das Kompilieren einer Mehrfachdatei-Assembly unterscheidet sich von der Kompilierung einer Einzeldatei-Assembly. Während bei einer einfachen Assembly ein Aufruf des C#-Compilers genügt, müssen wir bei einer Mehrfachdateiassemblierung jedes Quellcodemodul einzeln kompilieren und dabei nach bestimmten Kriterien eine gewisse Reihenfolge einhalten. Die Entscheidung, das Manifest in eine separate Datei zu kompilieren, zwingt uns sogar dazu, ein weiteres Tool des .NET Frameworks einzusetzen.
Die Entwicklung einer Mehrfachdateiassemblierung wird in diesem und dem folgenden Abschnitt im Detail gezeigt. Als Grundlage dienen dazu zwei eigenständige Projekte: TimerApp als Klassenbibliothek und ClientApp als Konsolenanwendung.
Im Projekt TimerApp wird eine Klasse namens Timer mit einer Methode ShowDateTime definiert. Diese Methode greift auf die Klassenmethode DateTime. Now zu und gibt die aktuelle Systemzeit und das Systemdatum an der Konsole aus.
| // ------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 14\MehrfachdateiAssembly\TimerApp |
| // ------------------------------------------------------------- |
| public class Timer { |
| public void ShowDateTime() { |
| Console.WriteLine(DateTime.Now); |
| } |
| } |
Das zweite Projekt ClientApp enthält in der Quellcodedatei Client.cs die Klasse Client mit der statischen Methode Main, die als Einstiegspunkt in die Anwendung dient. In der Methode wird ein Objekt vom Typ Timer der Anwendung TimerApp erzeugt, um die Methode ShowDateTime auszuführen. Da wir auf die externe Komponente TimerApp zugreifen, muss diese zuerst unter Verweise eingebunden werden, bevor wir deren Dienste in Anspruch nehmen können.
Damit wir später mehr Kombinationsmöglichkeiten zur Generierung der Mehrfachdatei-Assembly haben, enthält ClientApp mit der Klasse SecondClass in SecondClass.cs zusätzlich ein zweites Codemodul, dessen Methode GetOutput ebenfalls aus Main heraus aufgerufen wird.
| // ------------------------------------------------------- |
| // Beispiel: ...\Kapitel 14\MehrfachdateiAssembly\ClientApp |
| // ------------------------------------------------------- |
| using System; |
| using TimerApp; |
| namespace ClientApp { |
| public class Client { |
| static void Main(string[] args) { |
| Timer obj = new Timer(); |
| obj.ShowDateTime(); |
| SecondClass newClass = new SecondClass(); |
| newClass.GetOutput(); |
| Console.ReadLine(); |
| } |
| } |
| } |
| // Klassendefinition in einem eigenen Quellcodemodul |
| // des Projekts ClientApp |
| public class SecondClass { |
| public void GetOutput() { |
| Console.WriteLine("In der Klasse SecondClass"); |
| } |
| } |
Eine Mehrfachdatei-Assembly setzt sich aus mindestens einer NETMODULE-Datei und einer DLL- bzw. EXE-Datei zusammen – Ressourcendateien, die ebenfalls Bausteine einer Assembly sein können, lassen wir bei unserer Betrachtung außen vor. Jedes Element einer Assembly muss einzeln mit dem C#-Compiler kompiliert werden. Die Reihenfolge ist allerdings nicht beliebig, sondern folgt einer klaren Richtlinie. Sehen wir uns dazu den Quellcode der Klasse Client mit der Methode Main an. Wir gehen bei unseren Betrachtungen davon aus, dass alle Quellcodedateien des Projekts ClientApp in separate Dateien kompiliert werden. Würden wir versuchen, die Klasse Client mit
| csc Client.cs |
als Erstes zu kompilieren, wäre eine Fehlermeldung des Compilers die Folge. Das Problem resultiert aus der Tatsache, dass in Main ein Objekt vom Typ Timer instanziiert wird, der Compiler jedoch keine für ihn verwertbaren Typinformationen über Timer vorfindet. Es ist daher unumgänglich, zuerst die Bausteine einer Assembly zu kompilieren, die keinen Bezug zu anderen in der Anwendung definierten Typen haben.
Die Folge ist, dass wir zuerst Timer.cs kompilieren müssen. Da wir als Ergebnis eine Datei mit der Erweiterung NETMODULE benötigen, greifen wir auf die Option target des C#-Compilers zurück und geben das Attribut module an:
| csc /target:module Timer.cs |
Prinzipiell ist diese Anweisung richtig, erfordert aber einige Anmerkungen:
1. Die Dateien, aus denen sich eine Mehrfachdateiassemblierung zusammensetzt, müssen sich alle im gleichen Verzeichnis befinden. Empfehlenswert ist es deshalb, vor der ersten Kompilierung einen eigenen Ordner für die komplette Assembly anzulegen.| 2. | Die Kompilate werden genau in das Verzeichnis geschrieben, das der aktuellen Position der Eingabeaufforderung entspricht. Daher muss man nach dem Öffnen der Konsole zuerst in das entsprechende Verzeichnis wechseln. |
| 3. | Befindet sich das Quellcodemodul in einem Verzeichnis, das nicht der aktuellen Position der Eingabeaufforderung entspricht, müssen Sie den kompletten Pfad zu der Datei angeben. |
Gehen wir für die folgenden Beispiele von der Annahme aus, dass wir die assembly-bildenden Dateien in den Ordner C:\MyProject schreiben wollen. Die projektbildenden Dateien unserer Projekte sollen sich in den Verzeichnissen C:\TimerApp und C:\ClientApp befinden.
Nach dem Öffnen des Konsolenfensters müssen wir als Erstes in das Anwendungsverzeichnis C:\MyProject wechseln und können dann die folgende Anweisung geben:
| csc /target:module C:\TimerApp\Timer.cs |
Solange wir die korrekten Pfadangaben machen und sich der C#-Compiler in einem bekannten Suchpfad befindet, ist an dieser Anweisung nichts auszusetzen. Beabsichtigen wir, auch für die Quellcodedatei SecondClass.cs einen eigenen Baustein zu kompilieren, wird nun auch
| csc /target:module C:\ClientApp\SecondClass.cs |
fehlerfrei ausgeführt. Damit liegen im Verzeichnis C:\MyProject bereits die beiden kompilierten Dateien
| SecondClass.netmodule |
| Timer.netmodule |
vor, die beide die für sie spezifischen Typmetadaten und natürlich den IL-Code enthalten. Zum Abschluss fehlt noch die ausführbare EXE-Datei, die aus Client.cs generiert wird und das Manifest enthalten soll. Diese wird auch mit C#-Compiler erzeugt, allerdings muss dabei berücksichtigt werden, dass es zwei Abhängigkeiten gibt – nämlich die von den beiden zuvor kompilierten NETMODULE-Bausteine.
Um den Compiler nicht im Ungewissen zu lassen, welche Dateien zu einer Assemblierung gehören, setzt man einen anderen Optionsschalter des C#-Compilers: /addmodule. Vom Optionsschalter durch einen Doppelpunkt getrennt gibt man dahinter die Liste der Dateien an, deren Typinformationen zum fehlerfreien Kompilieren der aktuellen Datei erforderlich sind, z.B.:
| csc /addmodule:Timer.netmodule,SecondClass.netmodule C:\ClientApp\client.cs |
Beachten Sie, dass die einzelnen Module durch ein Komma getrennt werden. Nun haben Sie bereits Ihre erste Mehrfachdateiassemblierung erstellt, die sich aus drei Dateien zusammensetzt:
| Client.exe (diese Datei beinhaltet auch das Manifest) |
| Timer.netmodule |
| SecondClass.netmodule |
Wollen Sie die Anwendung verteilen, müssen Sie selbstverständlich alle drei Dateien an den Empfänger weitergeben.
Sehen wir uns zum Abschluss noch das Manifest der Mehrfachdatei-Assembly an (siehe Abbildung 14.6).

Hier klicken, um das Bild zu vergrößern
Abbildung 14.6 Das Manifest der Mehrfachdatei-Assembly »Client.exe«
Der Bezug der Assemblierung zu den Codemodulen wird durch zwei Einträge festgehalten. Jedes Codemodul, das an der Bildung der Assemblierung beteiligt ist, wird mit einem Hash-Code beschrieben, ebenso die Typen Timer und SecondClass mit der Angabe, in welchem Baustein der Typ enthalten ist.
Fassen wir an dieser Stelle noch einmal alle Schritte zusammen, die notwendig sind, um eine Mehrfachdatei-Assembly zu erzeugen, deren Manifest sich nicht in einer eigenen Datei befindet:
1. Zuerst werden alle CS-Quellcodedateien kompiliert, die das Manifest nicht enthalten und außerdem keine Referenz auf einen Typ in einem anderen Baustein der Assemblierung haben. Das Ergebnis ist in jedem Fall eine NETMODULE-Datei, die nur den IL-Code und die Typmetadaten enthält. In der Anweisung zur Kompilierung dieser Bausteine wird der Optionsschalter /target:module des C#-Compilers gesetzt.| 2. | Im zweiten Schritt kompiliert man die Bausteine zu NETMODULE-Dateien, die einen Typ in einem anderen Baustein referenzieren. Neben dem Optionsschalter /target kommt bei diesen Modulen auch der Schalter /addmodule zum Tragen. |
| 3. | Im letzten Schritt wird eine EXE- oder DLL-Datei kompiliert, die das Manifest enthält. Dabei werden alle an der Assemblierung beteiligten Module mit /addmodule zu einer logischen Einheit zusammengefügt. |
In diesem Abschnitt soll gezeigt werden, wie eine Assembly erstellt wird, deren Manifest in einer separaten Datei vorliegt. Die Schritte dazu unterscheiden sich von denen, die im letzten Abschnitt besprochen worden sind, weil sich dazu nicht mehr der C#-Compiler eignet, sondern ein anderes Tool des .NET Frameworks zum Einsatz kommt.
Zunächst sind wiederum zuerst die Quellcodemodule Timer.cs und SecondClass.cs in NETMODULE-Dateien zu kompilieren. Die Anweisungen dazu sind dieselben wie im letzten Abschnitt:
| csc /target:module C:\TimerApp\Timer.cs |
| csc /target:module C:\ClientApp\SecondClass.cs |
Da für das Manifest jetzt eine eigene Datei vorgesehen ist, wird die Datei Client.cs ebenfalls in eine NETMODULE-Datei kompiliert. Dabei muss die Bindung an das Modul Timer.netmodule berücksichtigt werden:
| csc /target:module /addmodule:Timer.netmodule, |
| SecondClass.netmodule C:\ClientApp\Client.cs |
Nun liegen drei Bausteine mit jeweils eigenen, typbeschreibenden Metadaten vor. Es fehlt noch die Datei, die das für die Laufzeit unentbehrliche Manifest mit den Metadaten der Assembly enthält. Der C#-Compiler ist nicht mehr dazu geeignet, eine allein stehende Datei zu erzeugen, die nur das Manifest enthält. Jetzt kommt ein anderes Tool des .NET Frameworks zum Einsatz: Es ist die Datei al.exe, die als Assembly Linker bezeichnet wird. Dieses Werkzeug bietet die Möglichkeit, aus einem oder mehreren Codemodulen beziehungsweise Ressourcendateien ein Manifest zu generieren. Wie schon der C#-Compiler, so wartet auch das AL-Tool mit einer Reihe von Optionsschaltern auf. Die meisten haben für unsere Zielsetzung jedoch keine Bedeutung.
Die komplette Anweisung, die uns die Brücke von den Modulen zu der Datei mit dem Manifest baut, ist schon ausgesprochen lang und passt nicht mehr in eine Buchzeile. Wenn Sie das Beispiel nachvollziehen wollen, schreiben Sie die folgende Anweisung bitte in eine Zeile:
| al /main:Client.Main /out:MultiFile.exe /target:exe |
| Client.netmodule Timer.netmodule SecondClass.netmodule |
Dem Assembly Linker müssen alle NETMODULE-Dateien bekannt gegeben werden, aus denen sich die Assembly zusammensetzt. In unserem Fall handelt es sich um:
| Client.netmodule |
| Timer.netmodule |
| SecondClass.netmodule |
Beachten Sie bitte, dass alle aufgeführten Dateien durch ein Leerzeichen getrennt werden.
Die Ausführung der Assembly muss an einem ganz bestimmten Punkt starten – in unserem Beispiel ist es die Methode Main im Modul Client. Das kann unsere Assemblierung natürlich nicht wissen, wir müssen ihr das ausdrücklich mitteilen. Dazu dient die Option /main, hinter der, getrennt durch einen Doppelpunkt, die Klasse mit der Startmethode angegeben wird.
Mit /out können Sie den Namen der Assembly frei bestimmen. Diese Angabe ist nicht optional, Sie müssen sie in jedem Fall beim Aufruf des Assembly Linkers machen. /target spezifiziert das Dateiformat. Es kommen nur drei Formate in Frage: exe für eine Konsolenanwendung, win für eine Windows-basierte Anwendung und lib für eine Codebibliothek.
Typen, die in einer Assemblierung enthalten sind, stehen grundsätzlich allen .NET-Anwendungen zur Verfügung, solange sie öffentlich definiert sind. Ist in einer Anwendung ein Verweis auf eine andere Assembly gelegt, können deren Dienste in Anspruch genommen werden.
| Hinweis Im Visual Studio 2002/2003 konnten nur Verweise auf DLL-Dateien eingebunden werden. Mit EXE-Dateien war das nicht möglich. Mit der Einführung von Visual Studio 2005 hat sich das geändert. |
Wie der Aufruf durch die Common Language Runtime zur Laufzeit erfolgt, ist damit allerdings noch nicht beschrieben, denn die Laufzeit weiß zwischen zwei grundsätzlich unterschiedlichen Assemblies zu unterscheiden: den privaten und den globalen. Mit dem .NET Framework 2.0 wurden darüber hinaus auch noch die befreundeten Assemblies eingeführt.
Angenommen Sie haben eine Klassenbibliothek entwickelt und kompiliert. Nehmen wir weiter an, der Name sei MyLibrary. Das Kompilat ist eine DLL-Datei, also eine Assembly (hier: MyLibrary.dll). Sie können auf die in dieser Assembly enthaltenen öffentlichen Typen zugreifen, wenn Sie in einer anderen Anwendung unter Verweise die Datei MyLibrary.dll angeben. Kompilieren Sie die Anwendung, wird im Ausgabeverzeichnis des Compilers nicht nur die Programmdatei (angenommen, sie heißt MyApp.exe) der Anwendung abgelegt, sondern gleichzeitig auch noch die Assembly MyLibrary.dll.
In einem Weitergabeszenario müssen Sie dem Endanwender beide Dateien bereitstellen, da die ausführbare Datei von der Assembly abhängt. Der Endanwender steht dann seinerseits in der Pflicht, MyLibrary.dll und MyApp.exe im gleichen physikalischen Verzeichnis zu installieren, damit die Anwendung fehlerfrei ausgeführt werden kann.
Nehmen wir nun weiter an, der Endanwender sei selbst .NET-Entwickler. Da er im Besitz von MyLibrary.dll ist, kann er in seinen Projekten auf die von Ihnen entwickelte Assembly verweisen und darauf zugreifen. Gibt er seine eigene Anwendung weiter, muss er neben der Programmdatei natürlich ebenfalls die Assembly MyLibrary.dll ausliefern.
In unseren fiktiven Annahmen spielen jetzt schon zwei Anwendungen eine Rolle, die beide dieselbe Assembly benutzen. Jede der beiden Anwendungen greift zur Laufzeit auf die Assembly MyLibrary.dll zu, die sich im jeweiligen Anwendungsverzeichnis befindet. Solche Assemblies werden als private Assemblies bezeichnet und sind der Standard. Das wesentliche Charakteristikum einer privaten Assembly ist, dass sie nur von einer Anwendung gesehen werden kann und benutzt wird.
Wir wollen das Gedankenspiel nun auf die Spitze treiben und annehmen, dass es mehrere Anwendungen gibt, die sich die Dienste von MyLibrary.dll sichern. Die Vorstellung, dass sich später ein Fehler in der Assembly herausstellt, ist nicht ganz abwegig. Wie sieht jetzt das Szenario aus, in dem die fehlerhafte MyLibrary.dll gegen eine neue Version ausgetauscht wird? Wenn auf einem Rechner mehrere Anwendungen installiert sind, die auf die Assembly zurückgreifen, müssen alle Abhängigleiten ausgetauscht werden. Wird eine übersehen, arbeitet die entsprechende Anwendung weiterhin fehlerhaft. Eigentlich ist das eine unzumutbare Situation.
Besser wäre es, MyLibrary.dll zentral zu installieren, denn dann könnten alle Anwendungen zuerst gemeinsam auf die alte, später auf die neue Version zugreifen. Hier setzen die globalen Assemblies an und bieten eine adäquate Lösung des Problems.
Eine gemeinsam genutzte Assembly, auch als globale Assembly bezeichnet, stellt ihre Dienste allen .NET-Anwendungen des Systems zur Verfügung. Das beste Beispiel globaler Assemblies sind die Klassen des .NET Frameworks. Die Entscheidung, ob eine Assembly global zur Verfügung stehen soll, muss schon bei der Kompilierung berücksichtigt werden, weil standardmäßig immer eine private Assembly erzeugt wird.
Gemeinsam genutzte Assemblies werden in einem speziellen Verzeichnis installiert: dem Global Assembly Cache (GAC). Der GAC ist eine Speicherlokalität, in der sogar mehrere Versionen derselben Assembly installiert werden dürfen, und unter Windows XP/2003 im Verzeichnis
\Windows\assembly
zu finden. In der Abbildung 14.7 sehen Sie die Anzeige des GAC im Explorer.
Der GAC ist so strukturiert, dass für jede eingetragene Assembly ein eigener Unterordner angelegt wird und für jede Version darin ein weiterer. Assemblies, die im GAC gespeichert sind, werden von der Common Language Runtime zwar benutzt, allerdings nicht in der Verweisliste der Entwicklungsumgebung angeboten.
Der Windows Explorer lässt es nicht zu, tiefer in das Verzeichnis \Windows\assembly »hineinzuschauen«. Sie können jedoch mit den alten DOS-Befehlen an der Eingabekonsole ohne weiteres die Struktur des GAC erkunden (siehe dazu auch Abbildung 14.8). Ausgehend von der Konsole lassen sich sogar die im GAC eingetragenen Dateien kopieren und stehen damit allgemein zur Verfügung.

Hier klicken, um das Bild zu vergrößern
Abbildung 14.7 Der »Global Assembly Cache« (GAC)
Die Common Language Runtime startet mit den zur Verfügung stehenden Informationen die Suche nach der entsprechenden Assembly im GAC. Findet die CLR die Assembly, wird sie geladen. War die Suche erfolglos, wird im Anwendungsverzeichnis weitergesucht, weil die CLR dann davon ausgehen muss, dass es sich um eine private handelt. Der Prozess der Suche ist ziemlich kompliziert, weil dabei unter anderem auch noch mögliche Vorgaben in den Konfigurationsdateien eine wichtige Rolle spielen, mit denen wir uns in Kapitel 27 noch beschäftigen werden.

Hier klicken, um das Bild zu vergrößern
Abbildung 14.8 Ein Blick in das Innere des Global Assembly Caches
Assemblies, die im GAC installiert sind, weisen gegenüber privaten Assemblies Merkmale auf, die von der CLR ausgewertet werden. Neben dem Namen wird die Identität einer globalen Assembly noch durch
| einen Schlüssel |
| eine Versionsnummer |
| die erforderliche Kulturinformation |
beschrieben. Assemblies, die diese Kriterien erfüllen, werden auch als Assemblies mit starkem Namen (Strong Named Assemblies) bezeichnet. Nur dann, wenn alle vier Kriterien erfüllt sind, lädt die CLR die Assembly aus dem GAC.
| Hinweis Auf die Kulturinformationen werden wir nicht weiter eingehen. Werden diesbezüglich keine besonderen Angaben gemacht, gilt eine Assembly als neutral. |
Globale Assemblies sind gekennzeichnet durch die Signierung mit einem binären Schlüsselpaar bestehend aus einem öffentlichen und einem privaten Schlüssel. Beide kryptografischen Schlüssel dienen einerseits zur Identifizierung einer Assembly und gewährleisten andererseits bei einer Änderung auch, dass der Autor der neuen Version derselbe ist wie der der alten Version. Nur der Entwickler der Ursprungsversion ist im Besitz von beiden Schlüsseln.
Beim Kompiliervorgang wird ein Teil des öffentlichen Schlüssels (Token) in das Manifest geschrieben und die Datei, die das Manifest enthält, mit dem privaten Schlüssel signiert. Der öffentliche Schlüssel ist ein Teil der Informationen, die eine Clientanwendung zur eindeutigen Identifikation einer bestimmten Assembly benötigt. Der private Schlüssel ist für den Aufrufer bedeutungslos, er sichert aber die Arbeit des Komponentenentwicklers und schützt gleichzeitig vor unbefugter Änderung einer globalen Assemblierung. Privater und öffentlicher Schlüssel korrespondieren miteinander, mit anderen Worten: Zu einem öffentlichen Schlüssel gehört auch ein bestimmter privater – das ist ein wichtiger Aspekt, der in seiner Bedeutung nicht hoch genug eingeschätzt werden darf.
Um eine Assembly mit einem Schlüssel zu signieren, ist eine Schlüsseldatei notwendig, die mit dem Tool sn.exe erzeugt wird.
Der Versionsnummer einer signierten Assembly hat eine ähnlich bedeutende Rolle wie der Schlüssel. Damit wird es möglich, mehrere Versionen einer Assembly auf demselben Computer auszuführen, die sich dann noch nicht einmal im öffentlichen Schlüssel unterscheiden.
Zur Laufzeit ermittelt die Common Language Runtime anhand der Versionsnummer, welche Version einer Assembly von einer Anwendung benutzt werden soll. Standardmäßig wird die Version geladen, die im Manifest der Anwendung angegeben ist. Dieses Verhalten lässt sich mit Hilfe von Konfigurationsdateien beeinflussen, wie in Kapitel 27 noch gezeigt wird.
Die Beschreibung einer Version folgt nach einer festgelegten Spezifikation. Jede Baugruppe hat eine Versionsnummer, die sich aus vier Elementen zusammensetzt, beispielsweise:
| 1.0.2.2 |
Die ersten beiden Zahlen beschreiben die Haupt- und Nebenversion. Werden an einer Komponente Änderungen vorgenommen, die inkompatibel zu der Vorgängerversion dieser Komponente sind, beispielsweise durch die Änderung der Parameterliste einer Methode, müssen sich die beiden Komponenten in der Haupt- oder Nebenversionsangabe unterscheiden, z.B.:
| 2.0.2.2 |
Eine abwärtskompatible Änderung wird durch die Elemente Build und Revision beschrieben. Änderungen, die über diese beiden Elemente bekannt gegeben werden, sind nur Korrekturen oder Fehlerbeseitigungen im Programmcode, die auf den Client keinen Einfluss ausüben – zumindest nicht im negativen Sinne, denn normalerweise dürfte ein Client von solchen Änderungen nur profitieren.

Hier klicken, um das Bild zu vergrößern
Abbildung 14.9 Das Schema der Versionierung
Die Versionsnummer wird vor der Kompilierung einer Assembly als Attribut in der Datei AssemblyInfo.cs festgelegt.
| [Assembly: AssemblyVersion("1.0.1.0")] |
Eine globale Assembly bereitzustellen erfordert zwei Arbeitsgänge:
1. Der Quellcode wird kompiliert und dabei eine vorher erzeugte oder bereits vorhandene Schlüsseldatei eingebunden. Damit ist die Assembly zur gemeinsamen Nutzung vorbereitet.| 2. | Das Kompilat muss im GAC installiert werden. |
Wenn wir davon ausgehen, die Assembly an einen Anwender weiterzugeben, benötigen wir zur Erfüllung des zweiten Punktes ein Installationsprogramm, das den Verteilungsprozess automatisiert. Hierzu bietet uns Visual Studio 2005 einen speziellen Projekttyp an, mit dem wir uns jedoch erst im letzten Kapitel dieses Buchs beschäftigen. Alternativ dazu stelle ich Ihnen aber ein Tool des .NET Frameworks vor, mit der sich die Assembly auch von der Konsole aus im GAC installieren lässt. Auf einer Entwicklermaschine ist das zu Testzwecken vollkommen ausreichend.
Eine Assembly im GAC zu installieren setzt einen starken Namen voraus. Das bedeutet, dass die Assembly mit einem Schlüssel signiert werden muss, der in einer Schlüsseldatei bereitgestellt wird. Hierzu stellt uns das .NET Framework mit der Datei sn.exe (sn = strong-named) ein passendes Kommandozeilentool zur Verfügung, das standardmäßig unter
\Programme\Microsoft Visual Studio 8\SDK\v2.0\Bin
zu finden ist.
Gesteuert wird das SN-Tool über diverse Optionsschalter. Der Schalter, mit dem ein Schlüsselpaar generiert wird, lautet -k. Hinter dem Schalter wird der Name der Schlüsseldatei angegeben – möglicherweise auch unter Angabe des Pfades, in dem die Datei gespeichert werden soll. Die Dateierweiterung einer Schlüsseldatei ist in der Regel SNK. Wollen Sie beispielsweise eine Schlüsseldatei namens MyKey.snk erzeugen und diese im Ordner C:\MyProject speichern, muss die Befehlszeile an der Eingabeaufforderung lauten:
| sn.exe –k C:\MyProject\MyKey.snk |
Das SN-Tool erfüllt nur Aufgaben, die in direktem Zusammenhang mit dem Schlüsselpaar stehen. So extrahiert es unter anderem mit dem Schalter -p aus einem Schlüsselpaar den öffentlichen Teil und speichert ihn in einer separat anzugebenden Datei. Benötigen Sie weitere Informationen zu den Optionsschaltern, schauen Sie bitte in der .NET-Dokumentation nach.
| Es ist sinnvoll, die Schlüsseldatei eines Projekt in dem Ordner abzulegen, in dem sich auch die Quellcodedateien befinden. Damit hat man alle projektrelevanten Dateien in einem Ordner und vermeidet das unbeabsichtigte Löschen der Schlüsseldatei. Das hätte nämlich fatale Konsequenzen zur Folge, wenn später ein Update der Komponente erforderlich werden sollte – Sie könnten sich dann nicht mehr als Autor der Komponente identifizieren, weil Sie eine neue Schlüsseldatei generieren müssten, die dann auch garantiert einen anderen Schlüsselsatz haben wird. |
Eleganter und einfacher ist das Erzeugen einer Schlüsseldatei, wenn Sie sich dazu des Visual Studios 2005 bedienen. Öffnen Sie dazu das Projekteigenschaftsfenster und wählen die Lasche Signierung. Markieren Sie anschließend die Auswahlbox Assembly signieren. Daraufhin wird die Auswahlliste aktiviert, welche die Suche nach einer bereits vorhandenen Schlüsseldatei oder das Erstellen einer neuen ermöglicht. Entscheiden Sie sich für letztgenannte Alternative, geben Sie in einem zusätzlichen Dialogfenster den Schlüsseldateinamen an. Darüber hinaus können Sie die Schlüsseldatei auch mit einem Kennwort schützen.
Beim Signieren einer Assembly haben Sie möglicherweise nicht immer Zugriff auf einen privaten Schlüssel. So kann ein Unternehmen beispielsweise ein stark gesichertes Schlüsselpaar haben, auf das die Entwickler nicht täglich zugreifen können. In diesem Fall müssen Sie eine verzögerte Signierung vornehmen, um zunächst nur den öffentlichen Schlüssel verfügbar zu machen. Markieren Sie hierzu den Optionsschalter Nur verzögerte Signierung. Das Hinzufügen des privaten Schlüssels wird bis zur Bereitstellung der Assembly verschoben.

Hier klicken, um das Bild zu vergrößern
Abbildung 14.10 Schlüsseldatei mit Visual Studio 2005 erzeugen
Signieren Sie unter Zuhilfenahme von Visual Studio 2005 Ihr Projekt, brauchen Sie keine weiteren Maßnahmen mehr zu ergreifen. Die Assembly ist sofort signiert und kann im GAC installiert werden. Nur die Version der Assemblierung sollten Sie, wie gleich beschrieben wird, richtig einstellen.
Anders sieht es aus, wenn Sie mit dem Kommandozeilentool die Schlüsseldatei erzeugt haben. Nun gilt es, diese dem Projekt zuzuordnen. Unterlassen Sie den nachfolgend beschriebenen Schritt, ist die Assemblierung nicht signiert und kann folgerichtig auch nicht als globale Assembly vom GAC aufgenommen werden.
Zum Einbinden der Schlüsseldatei müssen Sie in der Datei AssemblyInfo.cs das Attribut AssemblyKeyFile zusätzlich eintragen, dem der Name der Schlüsseldatei übergeben wird.
| [assembly: AssemblyKeyFile("..\\..\\MyKey.snk")] |
Berücksichtigen Sie, dass bei der Pfadangabe vom Verzeichnis der kompilierten Programmdatei ausgegangen wird. In einem zweiten Attribut, AssemblyVersion, geben Sie die Version der Assemblierung an, z.B.:
| [assembly: AssemblyVersion("1.0.0.3")] |
Diese Angabe wird nach der Installation im GAC des Explorers in der Spalte Version angezeigt. Damit sind alle notwendigen Voraussetzungen geschaffen, um eine Assembly mit starken Namen über das Menü Erstellen zu kompilieren.
Nach der Kompilierung ist die globale Assembly noch nicht im GAC eingetragen, so dass wir das selbst auf dem Entwicklungsrechner nachholen müssen. Grundsätzlich gibt es dazu zwei Alternativen:
| das Kommandozeilenprogramm gacutil des .NET Frameworks |
| eine Installationsroutine mit dem Micosoft Windows Installer |
Die Installationsroutine mit dem Windows Installer wird in Kapitel 27 vorgestellt, so dass wir uns an dieser Stelle dem Kommandozeilentool widmen. Nichtsdestotrotz sollten Sie eine Anwendung, insbesondere dann, wenn eine Assembly im GAC installiert werden soll, nur mit einer benutzergeführten Installationsroutine ausliefern, denn einem Anwender die Ausführung eines Kommandozeilenprogramms aufzubürden, ist schlicht und ergreifend unzumutbar.
Wie alle anderen Tools, so findet man gacutil.exe ebenfalls unter:
\Programme\Microsoft Visual Studio .NET 2003\SDK\v1.1\Bin
Die allgemeine Aufrufsyntax lautet:
| gacutil [Optionen] [Assemblyname] |
Aus der Liste der Optionen ragen zwei besonders heraus: der Schalter /i, um die darauf folgend angegebene Assembly im GAC zu installieren, und der Schalter /u, um eine gemeinsam genutzte Assembly zu deinstallieren, z.B.:
| gacutil /i MyGlobalAssembly.dll |
beziehungsweise
| gacutil /u MyGlobalAssembly |
Am Anfang des Kapitels habe ich Ihnen erzählt, dass im Manifest einer Assemblierung Informationen darüber stehen, von welchen anderen Komponenten sie abhängt. Dazu gehören unter anderem der Name und die Versionsnummer aller referenzierten Assemblies.
Wird eine referenzierte Assembly gegen eine mit einer höheren Versionsnummer ausgetauscht, ist die Ursache entweder die Bereitstellung zusätzlicher operativer Möglichkeiten oder die Beseitigung eines Bugs. Der Anwender sollte insbesondere im zuletzt aufgeführten Fall die ursprüngliche durch die neue Version ersetzen. Hierbei kommen Konfigurationsdateien ins Spiel, in denen entsprechende Umleitungen festgeschrieben werden können. An dieser Stelle werde ich aber nicht auf die Konfigurationsdateien eingehen und verweise stattdessen noch einmal auf Kapitel 27, in dem das Thema ausführlich behandelt wird.
Eben haben ich schon kurz die Datei AssemblyInfo.cs erwähnt. Sie diente dazu, gegebenenfalls hier den Pfad auf die Schlüsseldatei einer signierten Assembly anzugeben. Ganz allgemein dient diese Datei dazu, Zusatzinformationen zu der aktuellen Assemblierung bereitzustellen, beispielsweise eine Beschreibung, Versionsinformationen, Firmenname, Produktname und mehr. Diese werden im Windows Explorer in den Dateieigenschaften angezeigt. Da die Informationen die Assemblierung als Ganzes betreffen, müssen die Deklarationen außerhalb einer Klasse stehen und dürfen auch nur einmal gesetzt werden.
| [assembly: AssemblyTitle("AssemblyInfoDemo")] |
| [assembly: AssemblyDescription("Dieses Programm beschreibt |
| die Einträge in AssemblyInfo.cs")] |
| [assembly: AssemblyConfiguration("")] |
| [assembly: AssemblyCompany("Tollsoft")] |
| [assembly: AssemblyProduct("Visual C#-Buch")] |
| [assembly: AssemblyCopyright("Copyright © Tollsoft 2006")] |
| [assembly: AssemblyTrademark("")] |
| [assembly: AssemblyCulture("")] |
| [assembly: AssemblyVersion("1.0.0.0")] |
| [assembly: AssemblyFileVersion("1.0.0.0")] |

Hier klicken, um das Bild zu vergrößern
Abbildung 14.11 Die Eigenschaften einer Assemblierung im Windows Explorer
Sie können die gewünschten Assembly-Informationen in der Datei AssemblyInfo.cs eintragen wie oben gezeigt, Sie können aber auch die Einträge im Eigenschaftsdialog des Projekts vornehmen. Dazu öffnen Sie das Eigenschaftsfenster und wählen die Lasche Anwendung. Auf dieser Registerkarte sehen Sie die Schaltfläche Assembly-Information., über die der in der folgenden Abbildung 14.12 gezeigte Dialog geöffnet wird.

Hier klicken, um das Bild zu vergrößern
Abbildung 14.12 Eintragen der Assembly-Informationen in Visual Studio 2005
Um den Zugriff auf die Mitglieder einer Assembly von einer zweiten Assembly sicherzustellen, muss neben der Klasse auch das Mitglied public deklariert sein. Der Zugriffsmodifizierer internal einer Klasse macht die Klasse innerhalb einer Assembly zugreifbar, sie ist aber außerhalb der Assemblierung nicht zu sehen. Dasselbe gilt auch für die Member einer Klasse.
Um eine noch bessere Steuerung des Zugriffs zu ermöglichen, gibt es seit der Einführung von .NET Framework 2.0 Assemblierungen, die ihre internal-Klassen und Mitglieder nur ganz bestimmten Assemblierungen zur Verfügung stellen. Sehen Sie dazu das folgende Beispiel der Klasse Class1 an.
| namespace ClassLibrary { |
| class Class1 { |
| internal void TestMethod() { |
| Console.WriteLine("In der Friend-Assembly"); |
| } |
| } |
| } |
Da die Klasse über keinen Zugriffsmodifizierer verfügt, gilt sie per Vorgabe als internal. Andere Klassen, die sich innerhalb der gleichen Assemblierung befinden, sehen die Definition von Class1 und können darauf zugreifen. Bindet jedoch ein Client die Assemblierung ein, erfährt er nichts von der Existenz des Typs Class1.
Nehmen wir an, wir wollten nun den Typ Class1 einer ganz bestimmten Anwendung zur Verfügung stellen. Deren Name sei ConsoleAppliction. Wir müssen diese nur zu einer Friend Assembly heraufstufen, so dass sie die Ausnahme von der Regel darstellt.
Dazu bedarf es nur der Verknüpfung der Assemblierung, in der Class1 definiert ist, mit dem Attribut InternalsVisibleTo. Dem Attribut wird ein Zeichenfolgeparameter mit dem Namen der Assemblierung übergeben, für die alle nicht öffentlichen Typen sichtbar gemacht werden sollen.
| [assembly:InternalsVisibleTo("ConsoleApplication1")] |
| namespace ClassLibrary { |
| class Class1 {... } |
| } |
Das Attribut gehört zum Namespace System.Runtime.CompilerServices, der mit using bekannt gegeben werden sollte.
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.